import { splitPart, splitStream } from './utils';

export interface XStreamOptions<Output> {
  /**
   * @description Readable stream of binary data
   * @link https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream
   */
  readableStream: ReadableStream<Uint8Array>;

  /**
   * @description Support customizable transformStream to transform streams
   * @default sseTransformStream
   * @link https://developer.mozilla.org/en-US/docs/Web/API/TransformStream
   */
  transformStream?: TransformStream<string, Output>;
}

/**
 * @link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#fields
 * event 标识事件类型的字符串
 * data 消息的数据字段
 * id 事件id
 * retry 重连的时间
 */
export type SSEFields = 'data' | 'event' | 'id' | 'retry';

/**
 * @example
 * const sseObject = {
 *    event: 'delta',
 *    data: '{ key: "world!" }',
 * };
 */
export type SSEOutput = Partial<Record<SSEFields, any>>;

type XReadableStream<R = SSEOutput> = ReadableStream<R> & AsyncGenerator<R>;

/**
 * @description Transform Uint8Array binary stream to {@link SSEOutput} by default
 * @warning The `XStream` only support the `utf-8` encoding. More encoding support maybe in the future.
 */
function XStream<Output = SSEOutput>(options: XStreamOptions<Output>) {
  const { readableStream, transformStream } = options;
  if (!(readableStream instanceof ReadableStream)) {
    throw new Error(
      'The options.readableStream must be an instance of ReadableStream.'
    );
  }
  // Default encoding is `utf-8`
  // https://developer.mozilla.org/zh-CN/docs/Web/API/TextDecoderStream
  // 将二进制编码（如 UTF-8 等）的文本流转换为字符串流
  const decoderStream = new TextDecoderStream();

  // 流
  const stream = (
    transformStream
      ? // Uint8Array binary -> string -> Output
        readableStream.pipeThrough(decoderStream).pipeThrough(transformStream)
      : // Uint8Array binary -> string -> SSE part string -> Default Output {@link SSEOutput}
        readableStream
          .pipeThrough(decoderStream)
          // 内部也是使用 TransformStream
          .pipeThrough(splitStream())
          .pipeThrough(splitPart())
  ) as XReadableStream<Output>;

  /** support async iterator */
  stream[Symbol.asyncIterator] = async function* () {
    const reader = this.getReader();

    while (true) {
      const { done, value } = await reader.read();

      if (done) break;

      if (!value) continue;

      // Transformed data through all transform pipes
      yield value;
    }
  };

  return stream;
}

export default XStream;
